package mware_lib;

import java.io.IOException;
import java.io.Serializable;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import mware_lib.tcp.*;

public class ObjectBroker {  //- Front-End der Middleware -
  private NameService ns;
  private Server server;
  private IOThread io;
  private Map<Tuple<String/*adresse*/,Thread/*thread*/>,Client/*verbindung*/> connectionMap = new ConcurrentHashMap<>(); 
  
  private ObjectBroker(String serviceHost, int listenPort, IOThread io) {
    boolean initialized = false;
    int usedPort = BrokerPort;
    while (!initialized) {
      try {
        server = new Server(usedPort);
        initialized = true;
      } catch (IOException e) {   
        ++usedPort;
      }
    }
    this.io = io;
    System.out.println("Objectbroker auf Port "  + usedPort + " gestartet.");
    ns = new LocalNS(serviceHost, listenPort, usedPort);
    new ConnectionHandler(server).start();
  }  
  
  /** Liefert den Namensdienstproxy, der im Konsturktor erstellt wurde
   * 
   * @return Namensdienst-Proxy
   */
  public NameService getNameService() {
    return ns;
  }
   
  /** Beendet die Middleware.
   */
  public void shutDown() {
    try {
      server.shutdown();
    } catch (IOException ex) {
      //Was soll hier schon noch groß gemacht werden!?
    }
    for (Client c : connectionMap.values()) { //Alle offenen Verbindungen schließen
      try {
        c.close();
      } catch (IOException ex) {
        //Was soll hier schon noch groß gemacht werden!? War anscheinend bereits geschlossen
      }
    }
    io.interrupt();
    ns.shutdown(); //Ganz am Ende closen, es könnte ja ne RuntimeException geworfen werden
  }
  
  public Serializable invokeRemoteMethod(ObjectRef obj, String method, Serializable[] params) {
    //System.out.println("\nRufe entfernte Methode auf " + obj + "." + method + "(" + Arrays.toString(params) + ") Thread:" + Thread.currentThread().getName());
    if (!connectionMap.containsKey(new Tuple(obj.hostName,Thread.currentThread()))) {
      try {
        connectionMap.put(new Tuple(obj.hostName, Thread.currentThread()), new Client(obj.hostName, obj.port));
      } catch (UnknownHostException ex) {
        throw new RuntimeException("Beim öffnen der Verbindung für den entfernten Aufruf konnte die Zieladresse nicht aufgelöst werden!");
      } catch (IOException ex) {
        throw new RuntimeException("Beim öffnen der Verbindung für den entfernten Aufruf ist ein Fehler aufgetreten");
      }
    }
    Client connection = connectionMap.get(new Tuple(obj.hostName, Thread.currentThread()));
    try {
      connection.writeObject(new MethodCall(obj, method, params));
    } catch (IOException ex) {
      throw new RuntimeException("Während des Aufrufs ist ein Verbindungsproblem aufgetreten. Ist die Gegenstelle erreichbar?");
    }
    try {
      Serializable s = (Serializable)connection.readObject();
      //System.out.println("Result: " + s + "\n (" + Thread.currentThread() + ")");
      return s;
    } catch (IOException ex) {
      throw new RuntimeException("Beim warten auf die Antwort ist ein Verbindungsproblem aufgetreten. Wurde die Verbindung geschlossen?");
    }
  }
          
  
  
  
  //---------- STATICS --------------------
  
  public final static int BrokerPort = 9090;
  private static ObjectBroker ob;
  
  
  /** Diese Methode liefert eine Instanz des ObjektBrokers zurück und ist der 
   *  Einstiegspunkt der Middleware
   * 
   * @param serviceHost hostname des globalen Namensdienstes
   * @param listenPort  Port des globalen Namensdienstes
   * 
   * @return die neu erstellte ObjectBroker-Instanz
   */
  public static ObjectBroker init(String serviceHost, int listenPort) { 
    if (ob != null)
      throw new RuntimeException("Die Middleware wurde bereits initialisiert!");
    
    IOThread io = new IOThread();
    io.start();
    ob = new ObjectBroker(serviceHost, listenPort, io);    
    return ob;
  }
  
  /** Liefert die aktuelle Instanz des ObjectBrokers zurück. Dies wird für die
   *  Proxys benötigt.
   * 
   * @return die bereits initialisierte Instanz des ObjectBrokers oder null
   */
  public static ObjectBroker instance() {
    return ob;
  }
}



class ConnectionHandler extends Thread {
  private Server server;
  
  ConnectionHandler(Server s) {
    server = s;
  }
  
  @Override
  public void run() {
    try {
      while(true) {
        new ReqHandler(server.getConnection()).start();
      }        
    } catch (IOException e) {
      //Server wurde beendet
    }
  }
}

class ReqHandler extends Thread {
  private Connection conn;
  
  ReqHandler(Connection c) {
    conn = c;
  }
  
  @Override
  public void run() {
    try {
      while(true) {
        MethodCall call = (MethodCall)conn.readObject();
        conn.writeObject(ObjectBroker.instance().getNameService().invokeLocalMethod(call.object, call.methodName, call.paramArr));
      }
    } catch (IOException e) {
      //Server wurde anscheinend heruntergefahren, oder verbindung geschlossen
    }
  }
}